﻿using H.PK;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace H.RT
{
    public static class HDomain
    {
        /// <summary>
        /// 启动参数传递键
        /// </summary>
        public const string HRT_STARTPARAM = "HRT.START_PARAM";

        /// <summary>
        /// 程序集缓存存储键
        /// </summary>
        public const string HRT_ASSEMBLYDICT = "HRT.ASSEMBLY_DICTIONARY";

        /// <summary>
        /// 程序集映射存储键
        /// </summary>
        public const string HRT_RECORDDICT = "HRT.RECORD_DICTIONARY";

        /// <summary>
        /// 插件实例字典
        /// </summary>
        public const string HRT_PLUGINDICT = "HRT.PLUGIN_DICT";

        /// <summary>
        /// 工厂实例传递键
        /// </summary>
        public const string HRT_FACTORYINSTANCE = "HRT.FACTORY_INSTANCE";

        /// <summary>
        /// 程序基目录
        /// </summary>
        public const string HRT_APPBASE = "HRT.APPBASE";

        /// <summary>
        /// 应用程序的根目录(请在任意代码之前设置)
        /// </summary>
        public static string AppBase { get; private set; } = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

        /// <summary>
        /// 初始化环境变量
        /// </summary>
        public static void InitEnvironment(string appBase)
        {
            AppBase = appBase;
            Environment.SetEnvironmentVariable("PATH", $@"{Environment.GetEnvironmentVariable("PATH")};{Path.Combine(appBase, "lib")};{Path.Combine(appBase, "lib", Environment.Is64BitProcess ? "x64" : "x86")}");
        }

        /// <summary>
        /// 创建一个应用程序绑定信息
        /// </summary>
        /// <returns></returns>
        public static AppDomainSetup CreateDomainSetup()
        {
            return new AppDomainSetup
            {
                ApplicationBase = AppBase,
                PrivateBinPath = "lib"
            };
        }

        /// <summary>
        /// 创建一个受管理的应用程序域
        /// </summary>
        /// <param name="domainName">新应用程序域名称</param>
        /// <param name="setup">应用程序域配置对象</param>
        /// <returns></returns>
        public static AppDomain CreateDomain(string domainName, AppDomainSetup setup)
        {
            return AppDomain.CreateDomain(domainName, null, setup);
        }

        /// <summary>
        /// 创建一个受管理的应用程序域
        /// </summary>
        /// <param name="domainName">应用程序域</param>
        /// <param name="appBase">应用程序根目录</param>
        /// <returns></returns>
        public static AppDomain CreateDomain(string domainName)
        {
            return CreateDomain(domainName, CreateDomainSetup()).InitDomain();
        }

        /// <summary>
        /// 初始化应用程序域
        /// </summary>
        /// <param name="domain"></param>
        /// <returns></returns>
        public static AppDomain InitDomain(this AppDomain domain)
        {
            //设置代码集加载方法
            AppDomain.CurrentDomain.SetData(HRT_APPBASE, AppBase);
            if (domain.GetData(HRT_STARTPARAM) == null)
            {
                domain.AssemblyResolve += Domain_AssemblyResolve;
            }
            return domain;
        }

        /// <summary>
        /// 开始运行一个APP
        /// </summary>
        /// <param name="domain">运行APP的应用程序域</param>
        /// <param name="appFile">happ文件</param>
        /// <param name="args">启动参数</param>
        /// <returns>返回执行APP的任务,此等待Task结束即是等待APP结束</returns>
        public static Task ExecuteApps(this AppDomain domain, string appFile, params string[] args)
        {
            return Task.Factory.StartNew(() =>
            {
                try
                {
                    //将启动参数保存到应用程序域中
                    domain.SetData(HRT_STARTPARAM, new Tuple<string, string[]>(appFile, args));
                    //本地方式启动一个或多个APP
                    domain.DoCallBack(new CrossAppDomainDelegate(DomainCallback));
                }
                catch (Exception err)
                {
                    MessageBox.Show(err.ToString());
                }
            }, TaskCreationOptions.LongRunning);
        }

        /// <summary>
        /// 开始运行一个APP
        /// </summary>
        /// <param name="domain">运行APP的应用程序域</param>
        /// <param name="appBytes">APP的二进制数据</param>
        /// <param name="args">启动参数</param>
        /// <returns>返回执行APP的任务,此等待Task结束即是等待APP结束</returns>
        public static Task ExecuteApps(this AppDomain domain, byte[] appBytes, params string[] args)
        {
            return Task.Factory.StartNew(() =>
            {
                //将启动参数保存到应用程序域中
                domain.SetData(HRT_STARTPARAM, new Tuple<byte[], string[]>(appBytes, args));

                //本地方式启动一个或多个APP
                domain.DoCallBack(DomainCallback);
            }, TaskCreationOptions.LongRunning);
        }

        /// <summary>
        /// 子应用程序域回调函数
        /// </summary>
        private static void DomainCallback()
        {
            try
            {
                AppDomain domain = AppDomain.CurrentDomain;
                Thread thread = new Thread(ThreadHandler);
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                thread.Join();
            }
            catch (ThreadAbortException err) { throw err; }
            catch (Exception err)
            {
                MessageBox.Show(err.ToString());
            }
        }

        /// <summary>
        /// 线程回调函数
        /// </summary>
        private static void ThreadHandler()
        {
            AppDomain domain = AppDomain.CurrentDomain;
            try
            {
                HPackInfo packInfo;
                string[] args;
                if (domain.GetData(HRT_STARTPARAM) is Tuple<string, string[]> param)
                {
                    packInfo = HPackInfo.CreateInstance(File.ReadAllBytes(param.Item1));

                    args = param.Item2;
                }
                else if (domain.GetData(HRT_STARTPARAM) is Tuple<byte[], string[]> param2)
                {
                    packInfo = HPackInfo.CreateInstance(param2.Item1);
                    args = param2.Item2;
                }
                else
                {
                    throw new ArgumentNullException("ThreadHandler调用错误:必须传入启动参数");
                }

                Assembly assembly;
                if (packInfo.SymbolBytes != null)
                {
                    assembly = domain.Load(packInfo.AssemblyBytes, packInfo.SymbolBytes);
                }
                else
                {
                    assembly = domain.Load(packInfo.AssemblyBytes);
                }

                if (assembly == null)
                {
                    throw new Exception($"加载HAPP[{packInfo.PackageName}]失败");
                }

                Type entryType = assembly.EntryPoint.DeclaringType;
                if (entryType == null)
                {
                    throw new Exception($"无法加载HAPP[{packInfo.PackageName}]的入口类");
                }

                if (entryType.BaseType?.FullName != "H.IApp")
                {
                    throw new Exception($"HAPP[{packInfo.PackageName}]的入口类必须直接继承H.IApp类");
                }

                assembly.EntryPoint.Invoke(null, new object[] { args });
            }
            catch (ThreadAbortException)
            {
                //此处应当记录某个APP被结束运行
                return;
            }
            catch (Exception err)
            {
                MessageBox.Show(err.ToString());
            }
        }

        /// <summary>
        /// 用于处理各个应用程序域的指令集加载
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private static Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            AssemblyName assemblyName = new AssemblyName(args.Name);

            AppDomain domain = AppDomain.CurrentDomain;
            string appBase = AppBase;

            try
            {
                //创建Assembly缓存,仅对当前应用程序域有效
                if (!(domain.GetData(HRT_ASSEMBLYDICT) is Dictionary<string, Assembly> assemblyDictionary))
                {
                    assemblyDictionary = new Dictionary<string, Assembly>();
                    domain.SetData(HRT_ASSEMBLYDICT, assemblyDictionary);
                }
                if (!(domain.GetData(HRT_RECORDDICT) is HPackRecord packRecord))
                {
                    packRecord = new HPackRecord(Path.Combine(appBase, "plugin", HPackRecord.IndexFileName));
                    domain.SetData(HRT_RECORDDICT, packRecord);
                }

                if (assemblyDictionary.TryGetValue(assemblyName.Name, out Assembly assembly))
                {
                    return assembly;
                }


                string dllFile;

                //尝试加载hpk程序集
                if ((dllFile = packRecord.GetFakeName(assemblyName.Name)) != null)
                {
                    dllFile = Path.Combine(appBase, $@"plugin\{dllFile}.hpk");

                    if (File.Exists(dllFile))
                    {
                        HPackInfo packInfo = HPackInfo.CreateInstance(File.ReadAllBytes(dllFile));

                        if (packInfo.SymbolBytes != null)
                        {
                            assembly = domain.Load(packInfo.AssemblyBytes, packInfo.SymbolBytes);
                        }
                        else
                        {
                            assembly = domain.Load(packInfo.AssemblyBytes);
                        }

                        if (assembly?.EntryPoint != null)
                        {
                            //检查是否是插件(不是插件的话,仅加载不处理)
                            Type entryType = assembly.EntryPoint.DeclaringType;
                            if (entryType.BaseType?.FullName != "H.IPlugin")
                            {
                                throw new Exception("插件入口类必须直接继承H.IPlugin类");
                            }

                            if (!(domain.GetData(HRT_PLUGINDICT) is Dictionary<string, Type> pluginDictionary))
                            {
                                pluginDictionary = new Dictionary<string, Type>();
                                domain.SetData(HRT_PLUGINDICT, pluginDictionary);
                            }
                            pluginDictionary.Add(assemblyName.Name, entryType);
                        }
                    }
                }

                //检查是否是外部lib,是的话直接加载
                if (assembly == null)
                {
                    //尝试加载未加密的程序集
                    dllFile = Path.Combine(appBase, $@"lib\{assemblyName.Name}.dll");
                    if (File.Exists(dllFile))
                    {
                        assembly = Assembly.Load(File.ReadAllBytes(dllFile));
                    }
                }

                if (assembly != null)
                {
                    assemblyDictionary.Add(assemblyName.Name, assembly);
                }
                else
                {
                    return (from asm in domain.GetAssemblies() where asm.FullName == assemblyName.FullName select asm).FirstOrDefault();
                }

                return assembly;

            }
            catch (Exception err)
            {
                MessageBox.Show(err.ToString());
                return null;
            }
        }
    }
}
